package algebra.rewriting;

import java.util.HashSet;
import java.util.Set;

import kb.DescriptionLogicsKB;
import algebra.cost.PlanEnumeration;
import algebra.op.AlgebraicOP;
import algebra.op.JoinOP;
import algebra.op.ProjectionOP;
import algebra.op.ScanOP;
import algebra.op.SelectionOP;
import algebra.query.indexing.SecondaryIndex;
import algebra.rewriting.kfree.LangPd;

public class GeneratePlanSpace {

	private static final int MAX_PLAN = 200;
	private final AlgebraicOP op;
	// private final DescriptionLogicsKB kb;
	private final PlanEnumeration pe;

	private static enum SISelectionStrategy {
		RANDOM, USEFULNESS, BASIC
	};

	public GeneratePlanSpace(AlgebraicOP op, PlanEnumeration pe) {
		this.op = op;
		// this.kb = pe.getKB();
		this.pe = pe;
	}

	// apply rules to every op progressively
	public Set<AlgebraicOP> generate() {
		Set<AlgebraicOP> results = new HashSet<AlgebraicOP>();
		results.add(this.op);
		Set<AlgebraicOP> done = new HashSet<AlgebraicOP>();
		boolean quit = false;
		while (!done.containsAll(results) && !quit) {
			Set<AlgebraicOP> temp = new HashSet<AlgebraicOP>();
			for (AlgebraicOP sub : results) {
				if (done.contains(sub)) {
					continue;
				}
				temp.addAll(applyRewriting(sub));
				int total = results.size() + temp.size();
				System.err.println("# plans reached: " + total);
				if (total >= MAX_PLAN) {
					System.err.println("Final number of plans reached: "
							+ total);
					quit = true;
					break;
				}
				done.add(sub);
			}
			results.addAll(temp);
		}
		return results;
	}

	private Set<AlgebraicOP> applyRewriting(AlgebraicOP sop) {
		Set<AlgebraicOP> results = new HashSet<AlgebraicOP>();
		results.addAll(applyRewritingSingle(sop));
		// generate subqueries for rewriting
		if (sop instanceof JoinOP) {
			JoinOP ip = (JoinOP) sop;
			for (AlgebraicOP op1 : applyRewriting(ip.getFirstSubQuery())) {
				for (AlgebraicOP op2 : applyRewriting(ip.getSecondSubQuery())) {
					results.add(new JoinOP(op1, op2));
				}
			}
		} else if (sop instanceof ProjectionOP) {
			ProjectionOP jp = (ProjectionOP) sop;
			for (AlgebraicOP op1 : applyRewriting(jp.getSubquery())) {
				ProjectionOP rp = new ProjectionOP(jp.getPrjDesc(), op1,
						jp.getPlanEvalKB());
				// rp.setPlanEvalKB(jp.getPlanEvalKB());
				results.add(rp);
			}
		} else if (sop instanceof ScanOP) {
			ScanOP jp = (ScanOP) sop;
			for (AlgebraicOP op1 : applyRewriting(jp.getSubquery())) {
				ScanOP rp = new ScanOP(op1, jp.getSecondaryIndex());
				results.add(rp);
			}
		} else if (sop instanceof SelectionOP) {
			SelectionOP jp = (SelectionOP) sop;
			for (AlgebraicOP op1 : applyRewriting(jp.getSubquery())) {
				SelectionOP rp = new SelectionOP(op1,
						jp.getSelectionCondition(), jp.getPlanEvalKB());
				// rp.setPlanEvalKB(jp.getPlanEvalKB());
				results.add(rp);
			}
		}
		return results;
	}

	private Set<AlgebraicOP> applyRewritingSingle(AlgebraicOP sop) {
		Set<AlgebraicOP> ret = new HashSet<AlgebraicOP>();
		ret.add(sop);
		if (sop.isLeaf()) {
			return ret;
		}
		for (Set<SecondaryIndex> sis : this
				.selectSIByStrategy(SISelectionStrategy.BASIC)) {
			for (RewriteRule rule : RuleService.getRulePlain(sop, sis, pe)) {
				System.err.println("apply rule " + rule.getClass());
				AlgebraicOP rop = rule.rewrite();
				if (rop != null) {
					ret.add(rop);
				}
			}
		}
		return ret;
	}

	// get a set of sets of SIs: powerset
	private Set<Set<SecondaryIndex>> selectSIByStrategy(
			SISelectionStrategy strat) {
		if (strat == null) {
			strat = SISelectionStrategy.BASIC;
		}
		switch (strat) {
		case BASIC:
			Set<SecondaryIndex> sis = SecondaryIndex.allCachedQueries.keySet();
			return LangPd.powerSet(sis);
		default:
			break;
		}
		return null;
	}

}
